/**   
* @Title: AsyncClientManager.java 
* @Package net.gdface.httpclient 
* @Description: TODO 
* @author guyadong   
* @date 2015年5月28日 下午3:49:59 
* @version V1.0   
*/
package net.gdface.httpclient;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import net.gdface.utils.Assert;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 单实例对象<br>
 * 提供多线程共享的asyncclient
 * @author guyadong
 *
 */
public class AsyncClientManager {
	private static final Logger logger = LoggerFactory.getLogger(AsyncClientManager.class);
	private static AsyncClientManager singleton=null;

	/**
	 *默认线程池线程数(固定)
	 */
	private static final int DEFAULT_THREAD_POOL_SIZE=16;
	/**
	 * 连接管理器最大连接数
	 */
	private final int maxConnections;
	/**
	 * 每个主机最大连接数
	 */
	private final int maxConnectionsPerHost;
	/**
	 * 线程池 参见 {@link FirstRequestHandler#FirstRequestHandler(HttpGet, FutureCallback, boolean, ExecutorService)}
	 */
	private final ExecutorService executorService;
	private final CloseableHttpAsyncClient asyncClient;

	/**
	 * @return singleton
	 */
	public static AsyncClientManager getInstance() {
		if (null == singleton)
			throw new IllegalArgumentException(
					"instance没有初始化, 请先执行  createSingleton(int maxConnection, int maxConnectionPerHost)创建实例");
		return singleton;
	}
	/**
	 * 根据参数创建唯一实例
	 * @param maxConnection
	 * @param maxConnectionPerHost
	 * @param threadPoolSize
	 * @return
	 * @see #AsyncClientManager(int, int, int)
	 */
	public static AsyncClientManager createSingleton(int maxConnection, int maxConnectionPerHost, int threadPoolSize) {
		if (null == singleton)
			singleton = new AsyncClientManager(maxConnection,maxConnectionPerHost, threadPoolSize);
		return singleton;
	}
	/**
	 * 构造函数<br>
	 * 当参数小于等于0时，使用缺省值
	 * @param maxConnections 参见{@link #maxConnections}
	 * @param maxConnectionsPerHost 参见{@link #maxConnectionsPerHost}
	 * @param threadPoolSize 私有线程池的线程数 参见 {@link #executorService}
	 */
	private AsyncClientManager(int maxConnections, int maxConnectionsPerHost, int threadPoolSize) {
		this.maxConnections = maxConnections>0?maxConnections:HttpClientUtility.DEFAULT_MAX_CONNECTIONS;
		this.maxConnectionsPerHost = maxConnectionsPerHost>0?maxConnectionsPerHost:HttpClientUtility.DEFAULT_MAX_CONNECTIONS_PER_HOST;
		
		executorService = Executors.newFixedThreadPool(threadPoolSize>0?threadPoolSize:DEFAULT_THREAD_POOL_SIZE);
		//初始化异步对象
		asyncClient = HttpAsyncClients.custom()
			//.setDefaultIOReactorConfig(ioReactorConfig)
			.setDefaultRequestConfig(HttpClientUtility.BASE_REQUEST_CONFIG)
			//设置kee-alive策略 默认5秒
			//.setKeepAliveStrategy(FixedKeepAliveStrategy.DEFAULT_5SECONDS)
			// 关闭连接重用，减少连接资源占用
			.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE)
			//设置连接管理器最大连接数和每个主机最大连接数
			.setMaxConnTotal(this.maxConnections)
			.setMaxConnPerRoute(this.maxConnectionsPerHost)
			.build();
		logger.info("AsyncClient maxConnections(异步客户端最大连接数)=[{}],maxConnectionsPerHost(每主机最大连接数)=[{}]",
				this.maxConnections, this.maxConnectionsPerHost);

	}


	private CloseableHttpAsyncClient getClient() {
		if(!asyncClient.isRunning())
			asyncClient.start();
		return asyncClient;
	}
	public void close() throws IOException {
		if(asyncClient.isRunning())
			asyncClient.close();
			executorService.shutdownNow();
			logger.info("AsyncClient closed(异步客户端关闭)");
	}
	/**
	 * @return maxConnection
	 */
	public int getMaxConnection() {
		return maxConnections;
	}
	/**
	 * 调用{@code CloseableHttpAsyncClient}异步发送url请求,由{@code callback}处理返回结果。<br>
	 * @param request 请求目标
	 * @param callback 回调函数对象
	 * @return
	 * @see org.apache.http.impl.nio.client.CloseableHttpAsyncClient#execute(org.apache.http.client.methods.HttpUriRequest, FutureCallback)
	 */
	public Future<HttpResponse> getResponseAsync(HttpGet request, FutureCallback<HttpResponse> callback) {
		Assert.notNull(request, "request");
		Assert.notNull(callback, "callback");
		return getClient().execute(request, callback);
	}
	/**
	 * 
	 * (异步)向服务器发送HTTP请求
	 * @param request 请求目标
	 * @param firstRequest 执行{@code request}之前先要执行的目标,为null时直接请求{@code request}
	 * @param callback {@code request}的回调函数对象
	 * @return
	 * @see #getResponseAsync(HttpGet, FutureCallback)
	 */
	public Future<HttpResponse> getResponseAsync(HttpGet request, HttpGet firstRequest,
			FutureCallback<HttpResponse> callback) {
		Assert.notNull(request, "request");
		Assert.notNull(callback, "callback");
		return null == firstRequest ? getResponseAsync(request, callback) : getResponseAsync(firstRequest, new FirstRequestHandler(
				request, callback, true, executorService));
	}
	/**
	 * @param target
	 * @param config
	 * @param callback
	 * @param headers
	 * @return
	 * @see AsyncClientManager#getResponseAsync(URI, URI, RequestConfig, FutureCallback, NameValuePair...)
	 */
	public Future<HttpResponse> getResponseAsync(URI target, 
			RequestConfig config, FutureCallback<HttpResponse> callback, NameValuePair... headers) {
		return getResponseAsync(target, null,config,callback,headers);
	}
	/**
	 * 
	 * (异步)向服务器发送HTTP请求
	 * 
	 * @param target 请求目标
	 * @param firstTarget 执行{@code target}之前先要执行的目标,为null时直接访问{@code target}
	 * @param config 
	 *            ==null时使用默认的配置对象
	 * @param callback {@code target}的回调函数对象
	 * @param headers {@code target}的headers
	 * @return
	 * @see getResponseAsync
	 */
	public Future<HttpResponse> getResponseAsync(URI target, URI firstTarget,
			RequestConfig config, FutureCallback<HttpResponse> callback, NameValuePair... headers) {
		Assert.notNull(target, "target");
		Assert.notNull(callback, "callback");
		HttpGet request = HttpClientUtility.createHttpGET(target, config, headers);
		HttpGet firstRequest = null!=firstTarget?HttpClientUtility.createHttpGET(firstTarget, config):null;
		return getResponseAsync(request, firstRequest, callback);
	}

}
